Approval queue
ProApproval requests by what they need from the viewer. "Needs you" lists what they can decide, soonest to expire first; production requests are flagged.
Preview
Install
With the Candor CLI
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/approval-queue
Brings in: agent-avatar, approval-request, badge, locale, relative-time, work, utils · npm: lucide-react@^1.67.0, radix-ui@^1.8.1
Usage
"use client"
import * as React from "react"
import type { AgentIdentity } from "@/lib/chat"
import type { ApprovalRequest } from "@/lib/work"
import { ApprovalQueue } from "@/components/approval-queue"
const ago = (m: number) => new Date(Date.now() - m * 60_000).toISOString()
const ahead = (h: number) => new Date(Date.now() + h * 3_600_000).toISOString()
const keel: AgentIdentity = { id: "a-orch", name: "Keel", role: "orchestrator" }
const requests: ApprovalRequest[] = [
{
id: "ap1",
title: "Add region to customers",
summary: "A region column on customers, backfilled from billing country.",
requester: { agent: keel },
environment: "Acme web",
production: true,
artifactHash: "9f2c4e81b7a03d55c6e1f0a9b8d7c6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9",
impact: { components: [{ kind: "migration", name: "0142_add_customer_region" }], rows: "unknown" },
rollback: { available: true, how: "Run the migration's down step." },
createdAt: ago(25),
expiresAt: ahead(7 * 24),
state: "pending",
},
{
id: "ap2",
title: "Turn on plan-change-review",
summary: "Only billing admins can change a customer's plan while an invoice is open.",
requester: { agent: keel },
environment: "Acme web",
detail: "Staging",
production: false,
artifactHash: "77aa1c0e5d4b3a29180f7e6d5c4b3a2918f7e6d5c4b3a2918f7e6d5c4b3a2918",
impact: { components: [{ kind: "flag", name: "plan-change-review" }], rows: 0 },
rollback: { available: true, how: "Turn the flag off." },
createdAt: ago(60 * 3),
expiresAt: ahead(10),
state: "pending",
},
{
id: "ap3",
title: "Drop partner_tier_old",
summary: "A migration drops customers.partner_tier_old.",
requester: { name: "You" },
environment: "Acme web",
production: true,
artifactHash: "3b1d9c8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c",
impact: { components: [{ kind: "column", name: "customers.partner_tier_old" }], rows: "unknown" },
rollback: { available: false, reason: "a dropped column's data only comes back from a backup restore" },
createdAt: ago(60 * 20),
expiresAt: ahead(30),
state: "pending",
cannotDecide: "You asked for this change, so another approver must decide it.",
},
{
id: "ap4",
title: "Merge signup source values",
summary: "Merge “web” and “website” into one value.",
requester: { agent: keel },
environment: "Acme web",
production: true,
artifactHash: "c0de4f3e2d1c0b9a8f7e6d5c4b3a29180f7e6d5c4b3a29180f7e6d5c4b3a2918",
impact: { components: [{ kind: "column", name: "customers.signup_source" }], rows: 4210 },
rollback: { available: false, reason: "merged values can't be split back" },
createdAt: ago(60 * 26),
expiresAt: ahead(6 * 24),
state: "approved",
decision: { by: "Priya", at: ago(60 * 3) },
},
]
export default function Example() {
const [active, setActive] = React.useState("ap1")
return <ApprovalQueue className="max-w-md" requests={requests} activeId={active} onOpen={setActive} />
}<ApprovalQueue>
| Prop | Type | Default | Description |
|---|---|---|---|
| requestsrequired | ApprovalRequest[] | — | Every request; the tabs sort them into needs you, waiting on others and decided. |
| onOpen | ((id: string) => void) | undefined | — | Called with a request's id when its row is clicked. |
| activeId | string | undefined | — | The id of the request open beside the queue, marked as current. |
| className | string | undefined | — | Extra classes for the outer element. |
Strings
The fixed words, in English by default. Change any of them under approvalQueue in <LocaleProvider strings={{ … }}>; the table is exported as approvalQueueStrings.
| Key | English |
|---|---|
| label | Approval requests |
| tabs | Requests |
| mine | Needs you |
| others | Waiting on others |
| decided | Decided |
| emptyMine | Nothing waiting on you. |
| emptyOthers | Nothing waiting on others. |
| emptyDecided | No decisions yet. |
| production | Production |
| expires | expires |