use-canvases.ts3.5 KBView on GitHub
/**
 * useCanvases — loads canvases from the backend into canvasSlice.
 *
 * Implements the data-loading side of §8.1 (CanvasSliceState population).
 * Fetches all HomeView-pinned canvases for the current user and seeds them into
 * the Zustand canvasSlice (canvasesById + homeViewCanvasIds).
 */

import { useQuery } from '@tanstack/react-query';
import { useSession } from '@/modules/auth/utils/auth-client';
import { useTRPC } from '@/providers/query-provider';
import { useCedarStore } from '@/modules/store';
import { useEffect } from 'react';
import type { Canvas } from '@/modules/canvas/types/canvas-types';

/**
 * Fetch all HomeView-pinned canvases visible to the current user and
 * sync them into the canvasSlice store.
 *
 * Returns the raw query result (isLoading, error, etc.) for consumers
 * that need to react to loading state.
 */
export function useCanvases() {
  const { data: session } = useSession();
  const trpc = useTRPC();

  const upsertCanvas = useCedarStore((state) => state.upsertCanvas);
  const setHomeViewCanvasIds = useCedarStore((state) => state.setHomeViewCanvasIds);

  const query = useQuery(
    trpc.canvas.getCanvases.queryOptions(
      { homeViewOnly: true },
      {
        enabled: !!session?.user.id,
        staleTime: 30_000, // 30s — HomeView tabs rarely change mid-session
      },
    ),
  );

  // Sync fetched canvases into the Zustand canvasSlice
  useEffect(() => {
    if (!query.data) return;

    const rows = query.data as Array<{
      id: string;
      type: string;
      title: string;
      description: string | null;
      action_text: string | null;
      data: Canvas['data'];
      view_config: Canvas['viewConfig'];
      primary_owner: string;
      org_id: string | null;
      home_view_order: number | null;
      colour: string | null;
      icon: string | null;
      created_at: Date;
      updated_at: Date;
      effective_home_view_order: number | null;
      in_homeview: boolean;
    }>;

    // Map raw DB rows → Canvas objects
    const canvases: Canvas[] = rows.map((row) => ({
      id: row.id,
      type: row.type as Canvas['type'],
      title: row.title,
      description: row.description ?? undefined,
      actionText: row.action_text ?? undefined,
      data: row.data ?? {},
      viewConfig: row.view_config,
      primaryOwner: row.primary_owner,
      orgId: row.org_id ?? undefined,
      homeViewOrder: row.home_view_order ?? null,
      colour: row.colour ?? undefined,
      icon: row.icon ?? undefined,
      createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at),
      updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : String(row.updated_at),
    }));

    // Seed the store
    canvases.forEach((canvas) => upsertCanvas(canvas));

    // Set ordered HomeView tab IDs (sorted by effective_home_view_order)
    const homeViewRows = rows
      .filter((r) => r.in_homeview)
      .sort((a, b) => (a.effective_home_view_order ?? 0) - (b.effective_home_view_order ?? 0));

    setHomeViewCanvasIds(homeViewRows.map((r) => r.id));
  }, [query.data, upsertCanvas, setHomeViewCanvasIds]);

  // React Query sets isLoading = isPending && isFetching.
  // When the query is disabled (session not yet ready), isPending=true but isFetching=false,
  // so isLoading=false — which would incorrectly signal "no canvases" to HomeView.
  // Using isPending directly means: "we have no data yet" regardless of fetch status.
  // This correctly blocks the auto-open-new-tab logic until real data is available.
  return { ...query, isLoading: query.isPending };
}