RegistryUI recipes
Deal board
Deals as cards in pipeline stage columns, moved by drag and drop.
It loads its data on the server through the block's client; the deals, pipelines blocks must be installed (npx better-supabase add deals pipelines). Its page on bettersupabase.com is /recipes/deal-board.
Web, with shadcn components:
npx shadcn add card empty skeleton/**
* Render inside `<Suspense>`: one column per stage of the default deal
* pipeline. Dragging a deal saves its new stage through `moveDeal`.
*/
export async function DealBoard() {
const [session, t] = await Promise.all([getSession(), getExtracted("crm")]);
if (!can(session, "deals.read")) {
forbidden();
}
const board = await settle(loadDealBoard);
if (board.kind === "no-organization") {
return null;
}
const pipeline = board.kind === "ok" ? board.data.pipeline : null;
const openStages =
board.kind === "ok"
? board.data.stages
.filter((stage) => stage.category === "open")
.map((stage) => ({ value: stage.id, label: stage.name }))
: [];
return (
<Card data-recipe="deal-board">
<CardHeader>
<CardTitle>{pipeline?.name ?? t("Deals")}</CardTitle>
<CardDescription>
{t("Drag a deal to another stage to move it.")}
</CardDescription>
{pipeline && can(session, "deals.write") ? (
<CardAction>
<CreateDealDialog pipelineId={pipeline.id} stages={openStages} />
</CardAction>
) : null}
</CardHeader>
<CardContent>
{board.kind === "error" ? (
<CrmLoadError title={t("The board didn't load")} />
) : board.data.pipeline === null ? (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<KanbanIcon />
</EmptyMedia>
<EmptyTitle>{t("No pipeline yet")}</EmptyTitle>
<EmptyDescription>
{can(session, "pipelines.manage")
? t(
"Start with six stages, from qualified to closed won or lost.",
)
: t("Ask an admin to create the sales pipeline.")}
</EmptyDescription>
</EmptyHeader>
{can(session, "pipelines.manage") ? (
<EmptyContent>
<CreatePipelineButton />
</EmptyContent>
) : null}
</Empty>
) : (
<DealKanban
key={board.data.deals
.map((deal) => `${deal.id}:${deal.stageId}`)
.join(",")}
stages={board.data.stages}
deals={board.data.deals}
canMove={can(session, "deals.write")}
/>
)}
</CardContent>
</Card>
);
}Last updated on