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 };
}