use-setup-pipeline.ts4.4 KBView on GitHub
import { useCallback, useMemo, useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'sonner';
import {
  CONVERSATION_CANVAS_TEMPLATES,
  TEAM_TEMPLATES,
  buildCanvasViewConfig,
  type CanvasTemplate,
} from '@/modules/home/components/NewCanvasScreen';
import { useCanvases } from '@/modules/canvas/hooks/use-canvases';
import { useHasCrmConnected } from '@/modules/integrations/use-has-crm-connected';
import { useTRPC } from '@/providers/query-provider';
import { useCedarStore } from '@/modules/store';
import { CEDAR_COLORS } from '@/components/ui/SexyColourPicker';
import type { Canvas } from '@/modules/canvas/types/canvas-types';

/**
 * The pipeline views on offer during setup: the four conversation canvases plus
 * the Kanban board. Templates the gallery marks `disabled` are left out — a card
 * you cannot click has no place in a flow whose whole job is making choices.
 */
export const SETUP_PIPELINE_TEMPLATES: CanvasTemplate[] = [
  ...CONVERSATION_CANVAS_TEMPLATES,
  ...TEAM_TEMPLATES.filter((template) => !template.disabled && template.id === 'kanban-view'),
];

export function useSetupPipeline() {
  const trpc = useTRPC();
  const [pending, setPending] = useState<string[]>([]);

  useCanvases();
  const canvasesById = useCedarStore((state) => state.canvasesById);
  const homeViewCanvasIds = useCedarStore((state) => state.homeViewCanvasIds);
  const upsertCanvas = useCedarStore((state) => state.upsertCanvas);
  const setHomeViewCanvasIds = useCedarStore((state) => state.setHomeViewCanvasIds);
  const hasCrmConnected = useHasCrmConnected();

  const { mutateAsync: createCanvas } = useMutation(
    trpc.canvas.createCanvas.mutationOptions(),
  );

  const tabs = useMemo(
    () =>
      homeViewCanvasIds
        .map((id) => canvasesById[id])
        .filter((canvas): canvas is Canvas => !!canvas),
    [homeViewCanvasIds, canvasesById],
  );

  const has = useCallback(
    (template: CanvasTemplate) =>
      pending.includes(template.id) ||
      tabs.some((canvas) => canvas.title === template.title),
    [pending, tabs],
  );

  const add = useCallback(
    async (template: CanvasTemplate) => {
      if (has(template)) return;
      setPending((current) => [...current, template.id]);
      try {
        const created = await createCanvas({
          title: template.title,
          type: template.type,
          description: template.description,
          viewConfig: buildCanvasViewConfig(template.type, { hasCrmConnected }),
          data: {},
          homeViewOrder: homeViewCanvasIds.length,
          // Deterministic per template rather than random: two users who pick the
          // same views get the same colours, and re-running setup does not
          // repaint the tab strip.
          colour: CEDAR_COLORS.dark[
            SETUP_PIPELINE_TEMPLATES.findIndex((t) => t.id === template.id) %
              CEDAR_COLORS.dark.length
          ]!,
        });

        const canvas: Canvas = {
          id: created.id,
          type: created.type as Canvas['type'],
          title: created.title,
          description: created.description ?? undefined,
          data: (created.data as Canvas['data']) ?? {},
          viewConfig: created.viewConfig as Canvas['viewConfig'],
          primaryOwner: created.primaryOwner,
          orgId: created.orgId ?? undefined,
          homeViewOrder: created.homeViewOrder ?? null,
          colour: (created as { colour?: string }).colour ?? undefined,
          icon: (created as { icon?: string }).icon ?? undefined,
          createdAt:
            created.createdAt instanceof Date
              ? created.createdAt.toISOString()
              : String(created.createdAt),
          updatedAt:
            created.updatedAt instanceof Date
              ? created.updatedAt.toISOString()
              : String(created.updatedAt),
        };

        upsertCanvas(canvas);
        setHomeViewCanvasIds([...homeViewCanvasIds, canvas.id]);
      } catch (error) {
        toast.error(
          error instanceof Error ? error.message : `Couldn't add ${template.title}`,
        );
      } finally {
        setPending((current) => current.filter((id) => id !== template.id));
      }
    },
    [
      has,
      createCanvas,
      hasCrmConnected,
      homeViewCanvasIds,
      upsertCanvas,
      setHomeViewCanvasIds,
    ],
  );

  return { templates: SETUP_PIPELINE_TEMPLATES, tabs, has, add, hasCrmConnected };
}